/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable no-multi-assign */
import { effect } from './singals';
import {
    safeCall, noop, EFFECT_KEY, DISPOSE_KEY, SSCLASS_KEY
} from './utils';

function collectEffectMethod(target: any, method: Function) {
    const set = target[EFFECT_KEY] = target[EFFECT_KEY] || new Set([]);
    if (set.has(method)) {
        return;
    }
    set.add(method);
}

function collectDisposeMethod(target: any, method: Function) {
    const set = target[DISPOSE_KEY] = target[DISPOSE_KEY] || new Set([]);
    if (set.has(method)) {
        return;
    }
    set.add(method);
}

function createOnLoad(onLoad: VoidFunction) {
    return function _onLoad() {
        safeCall(onLoad, this);

        const effectList = this[EFFECT_KEY] || [];

        for (const effectFn of effectList) {
            const dispose = effect(() => safeCall(effectFn, this));
            collectDisposeMethod(this, dispose);
        }
    };
}

function createOnEnable(onEnable: VoidFunction) {
    return function _onEnable() {
        safeCall(onEnable, this);
    };
}

function createOnDisable(onDisable: VoidFunction) {
    return function _onDisable() {
        const disposeList = this[DISPOSE_KEY] || [];

        for (const disposeFn of disposeList) {
            disposeFn.call(this);
        }

        safeCall(onDisable, this);
    };
}

function createOnDestroy(onDestroy: VoidFunction) {
    return function _onDestroy() {
        safeCall(onDestroy, this);
    };
}

export const ssclass: ClassDecorator = (target: any) => {
    const originOnLoad = target.prototype.onLoad;
    const originOnDestroy = target.prototype.onDestroy;
    const originOnEnable = target.prototype.onEnable;
    const originOnDisable = target.prototype.onDisable;

    target.prototype.onLoad = createOnLoad(originOnLoad);
    target.prototype.onEnable = createOnEnable(originOnEnable);
    target.prototype.onDisable = createOnDisable(originOnDisable);
    target.prototype.onDestroy = createOnDestroy(originOnDestroy);
    target.prototype[SSCLASS_KEY] = true;
    return target;
};

export const sseffect: MethodDecorator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
    const method = target[propertyKey];

    collectEffectMethod(target, method);

    descriptor.writable = false;
    descriptor.value = noop;
    return descriptor;
};
